{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "source": [
    "import torch\n",
    "rnn = torch.nn.RNN(20,50,2)  #输入特征维度，隐藏层的神经元数量，也就是隐藏层的特征数量。循环神经网络的层数，默认值是 1。\n",
    "input = torch.randn(100,32,20) #默认情况下第一个维度是序列的长度， 第二个维度才是 - - batch，第三个维度是特征数目。\n",
    "h_0 = torch.randn(2,32,50) #第一次输入的隐藏层输入特征；第一个维度是循环神经网络的层数，默认值是 1,第二个维度才是 - - batch，第三个维度隐藏层的神经元数量，也就是隐藏层的特征数量，\n",
    "output,hn = rnn(input,h_0)\n",
    "print(output.size(),hn.size())"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "torch.Size([100, 32, 50]) torch.Size([2, 32, 50])\n"
     ]
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "![](2021-10-17-15-41-06.png)  \n",
    "![](2021-10-17-15-42-41.png)  "
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "source": [
    "class RNN(object):\n",
    "    def __init__(self, input_size, hidden_size):\n",
    "        super().__init__()\n",
    "        self.W_xh = torch.nn.Linear(input_size, hidden_size) #因为最后的操作是相加 所以hidden要和output的shape一致\n",
    "        self.W_hh = torch.nn.Linear(hidden_size, hidden_size)\n",
    "    def __call__(self, x, hidden):\n",
    "        return self.step(x,hidden)\n",
    "    def step(self, x, hidden):\n",
    "        h1 = self.W_hh(hidden)\n",
    "        w1 = self.W_xh(x)\n",
    "        out = torch.tanh(h1+w1)\n",
    "        hidden  = self.W_hh.weight\n",
    "        return out, hidden\n",
    "\n",
    "rnn = RNN(20,50)\n",
    "input = torch.randn(32,20)\n",
    "h_0 = torch.randn(32,50)\n",
    "seq_len = input.shape[0]\n",
    "for i in range(seq_len):\n",
    "    output, hn = rnn(input[i,:], h_0)\n",
    "print(output.size(), h_0.size())"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "torch.Size([32, 50]) torch.Size([32, 50])\n"
     ]
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "\n",
    "LSTM\n",
    "LSTM 是 Long Short Term Memory Networks 的缩写，按字面翻译就是长的短时记忆网络。LSTM 的网络结构是 1997 年由 Hochreiter 和 Schmidhuber 提出的，随后这种网络结构变得非常流行。 LSTM虽然只解决了短期依赖的问题，并且它通过刻意的设计来避免长期依赖问题，这样的做法在实际应用中被证明还是十分有效的，有很多人跟进相关的工作解决了很多实际的问题，所以现在LSTM 仍然被广泛地使用。\n",
    "标准的循环神经网络内部只有一个简单的层结构，而 LSTM 内部有 4 个层结构：\n",
    "\n",
    "第一层是个忘记层：决定状态中丢弃什么信息\n",
    "\n",
    "第二层tanh层用来产生更新值的候选项，说明状态在某些维度上需要加强，在某些维度上需要减弱\n",
    "\n",
    "第三层sigmoid层（输入门层），它的输出值要乘到tanh层的输出上，起到一个缩放的作用，极端情况下sigmoid输出0说明相应维度上的状态不需要更新\n",
    "\n",
    "最后一层决定输出什么，输出值跟状态有关。候选项中的哪些部分最终会被输出由一个sigmoid层来决定。\n",
    "\n",
    "pytorch 中使用 nn.LSTM 类来搭建基于序列的循环神经网络，他的参数基本与RNN类似，这里就不列出了。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "source": [
    "lstm = torch.nn.LSTM(10,20,2)\n",
    "input = torch.randn(5,3,10)\n",
    "h0 = torch.randn(2,3,20)\n",
    "c0 = torch.randn(2,3,20)\n",
    "output, hn = lstm(input, (h0, c0)) #Outputs: output, (h_n, c_n)\n",
    "print(output.size(), hn[0].size(), hn[1].size())"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "torch.Size([5, 3, 20]) torch.Size([2, 3, 20]) torch.Size([2, 3, 20])\n"
     ]
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "GRU¶\n",
    "GRU 是 gated recurrent units 的缩写，由 Cho在 2014 年提出。GRU 和 LSTM 最大的不同在于 GRU 将遗忘门和输入门合成了一个\"更新门\"，同时网络不再额外给出记忆状态，而是将输出结果作为记忆状态不断向后循环传递，网络的输人和输出都变得特别简单。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "source": [
    "rnn = torch.nn.GRU(10,20,2)\n",
    "input = torch.randn(5,3,10)\n",
    "h_0 = torch.randn(2,3,20)\n",
    "output, hn = rnn(input, h0)\n",
    "print(output.size(), hn.size())"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "torch.Size([5, 3, 20]) torch.Size([2, 3, 20])\n"
     ]
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "2.5.3 循环网络的向后传播（BPTT）\n",
    "在向前传播的情况下，RNN的输入随着每一个时间步前进。在反向传播的情况下，我们“回到过去”改变权重，因此我们叫它通过时间的反向传播（BPTT）。\n",
    "\n",
    "我们通常把整个序列（单词）看作一个训练样本，所以总的误差是每个时间步（字符）中误差的和。权重在每一个时间步长是相同的（所以可以计算总误差后一起更新）。\n",
    "\n",
    "使用预测输出和实际输出计算交叉熵误差\n",
    "网络按照时间步完全展开\n",
    "对于展开的网络，对于每一个实践步计算权重的梯度\n",
    "因为对于所有时间步来说，权重都一样，所以对于所有的时间步，可以一起得到梯度（而不是像神经网络一样对不同的隐藏层得到不同的梯度）\n",
    "随后对循环神经元的权重进行升级\n",
    "RNN展开的网络看起来像一个普通的神经网络。反向传播也类似于普通的神经网络，只不过我们一次得到所有时间步的梯度。如果有100个时间步，那么网络展开后将变得非常巨大，所以为了解决这个问题才会出现LSTM和GRU这样的结构。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "在 PyTorch 中我们用 nn.Embedding 层来做嵌入词袋模型，Embedding层第一个输入表示我们有多少个词，第二个输入表示每一个词使用多少维度的向量表示。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "source": [
    "# an Embedding module containing 10 tensors of size 3\n",
    "embedding = torch.nn.Embedding(10, 3)\n",
    "# a batch of 2 samples of 4 indices each\n",
    "input = torch.LongTensor([[1, 2, 4, 5], [4, 3, 2, 9]])\n",
    "output = embedding(input)\n",
    "print(output.size())"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "torch.Size([2, 4, 3])\n"
     ]
    }
   ],
   "metadata": {}
  }
 ],
 "metadata": {
  "orig_nbformat": 4,
  "language_info": {
   "name": "python",
   "version": "3.8.10",
   "mimetype": "text/x-python",
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "pygments_lexer": "ipython3",
   "nbconvert_exporter": "python",
   "file_extension": ".py"
  },
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3.8.10 64-bit ('d2l-zh': conda)"
  },
  "interpreter": {
   "hash": "0bedf3452ed48e38a2b75ff9f66ce8ae9ae2e92e8665cb618ee0797d2f46b9e5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}